using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using Microsoft.SqlServer.Dts.Runtime;
using System.Collections;

namespace Microsoft.SharedSource.SqlServer.MDDE.Components
{
    [
        System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1705:LongAcronymsShouldBePascalCased"),
        SupportedComponentType("Microsoft.SqlServer.Dts.Runtime.Variable")
    ]
    public sealed class MDDEVariable : MDDEComponent
    {
        #region Member Variables

        private XmlNodeList changedProperties = null;
        private Variable component = null;

        #endregion

        public Variable InnerVariable
        {
            get
            {
                return (Variable)innerComponent;
            }
        }

        public MDDEVariable(Package package, Object component, string name, string uri)
            : base(package, component, name, uri)
        {
        }

        #region Overriden Methods

        public override void GetChangeXml(MDDEComponent subject, XmlDocument changeXml)
        {
            #region Set Values

            if (subject == null)
                throw new ArgumentNullException("Subject can not NULL");

            if (!(subject is MDDEVariable))
                throw new ArgumentException("Subject is not of type MDDEVariable");
                        
            component = (Variable)innerComponent;            
            Variable referenceComponent = (Variable)subject.InnerComponent;

            DtsProperties componentPropertyCollection = (DtsProperties)component.Properties;
            DtsProperties referenceComponentPropertyCollection = (DtsProperties)referenceComponent.Properties;

            XmlNode variableNode = null;
            variableNode = changeXml.CreateElement("Variable");

            bool nodeEmpty = true;

            #endregion

            #region Compare properties of Variable with subject and create Properties' Xml
            
            for (int propertyIndex = 0; propertyIndex < componentPropertyCollection.Count; propertyIndex++)
            {
                if (componentPropertyCollection[propertyIndex].Name.CompareTo("Name") == 0)
                    continue;

                DtsProperty componentProperty = componentPropertyCollection[propertyIndex];
                DtsProperty referenceComponentProperty = referenceComponentPropertyCollection[propertyIndex];

                if ((componentProperty.Get && componentProperty.Set)                         
                    && (referenceComponentProperty.Get && referenceComponentProperty.Set))                        
                {
                    string componentPropertyValue = String.Empty;
                    if (componentProperty.GetValue(component) != null)
                        componentPropertyValue = componentProperty.GetValue(component).ToString();

                    string referenceComponentPropertyValue = String.Empty;
                    if (referenceComponentProperty.GetValue(referenceComponent) != null)
                        referenceComponentPropertyValue = referenceComponentProperty.GetValue(referenceComponent).ToString();

                    if (componentPropertyValue != referenceComponentPropertyValue)
                    {
                        UpdateNodeProperties(changeXml, variableNode, componentProperty.Name, componentPropertyValue);
                        nodeEmpty = false;
                    }
                }
            }

            #endregion            

            #region Update Changes Xml

            if (!nodeEmpty)
            {                
                AddAttribute(changeXml, variableNode, "Name", this.name);
                AddAttribute(changeXml, variableNode, "URL", this.uri);

                changeXml.FirstChild.AppendChild(variableNode);
            }

            #endregion
        }

        public override bool ApplyChanges(XmlDocument changeXml, MDDEExecutionDAG originalPackage)
        {
            try
            {
                #region Set Values

                XmlNode componentChangeXml =
                    changeXml.SelectSingleNode("//PackageChanges/Variable[@URL='" + this.uri + "']");

                if (componentChangeXml == null)
                    return true;

                //if (innerComponent == null)
                //    return false;

                //if (!(innerComponent is Variable))
                //    return false;

                component = (Variable)innerComponent;
                DtsContainer parentComponent = (DtsContainer)this.predecessors[0].InnerComponent;

                string variableName = component.QualifiedName.ToString();

                #endregion

                #region Read Change Xml

                if (componentChangeXml.SelectSingleNode("Properties") != null)
                    changedProperties = componentChangeXml["Properties"].ChildNodes;

                if (changedProperties == null)
                    return true;

                #endregion

                #region Get Parent Component's Variable Collection and Dispenser

                Variables variableCollection = null;
                VariableDispenser variableDispenser = null;

                variableCollection = parentComponent.Variables;
                variableDispenser = parentComponent.VariableDispenser;

                if (variableCollection == null || variableDispenser == null)
                    return false;

                #endregion

                #region Lock Variable in Read/Write Mode

                if (!variableCollection.Contains(variableName))
                    return false;

                try
                {
                    variableDispenser.LockForWrite(variableName);
                }
                catch
                {
                    return false;
                }

                Variables lockedVariables = null;
                variableDispenser.GetVariables(ref lockedVariables);

                #endregion

                #region Apply Properties

                ApplyProperties(variableName, lockedVariables);

                #endregion

                #region Release Variable

                lockedVariables.Unlock();

                #endregion

                return true;
            }

            catch
            {
                // TODO: Add to Log
                return false;
            }
        }

        public override XmlDocument GetComponentTree()
        {
            XmlDocument xVariable = new XmlDocument();

            // Create the 'Node' node.
            XmlElement xRoot = AppendChildElement( xVariable, "Node",
                new string[]{
                    "Type", "Variable", "Name", InnerVariable.Name,
                    "Uri", Uri, "ClassId", "Microsoft.SqlServer.Dts.Runtime.Variable",
                    "RowsetDescriptorType", string.Empty, "ContainsMappings", "False",
                    "ContainsCollections", "False"
                });

            // Append the properties.
            base.AppendDtsProperties(xRoot, (IDTSPropertiesProvider)InnerVariable);

            return xVariable;
        }

        public override bool Validate()
        {
            return true;
        }

        #endregion

        #region Helper Methods

        private object GetParentComponent(string componentURI, out ObjectType componentType)
        {
            string parentcomponentURI = componentURI.Substring(0, (componentURI.LastIndexOf("/")));
            componentType = ObjectType.Package;
            return GetComponent(this.package, parentcomponentURI, ref componentType);
        }

        private void ApplyProperties(string variableName, Variables lockedVariables)
        {
            DtsProperties dtsProperties = lockedVariables[variableName].Properties;

            foreach (XmlNode changedProperty in changedProperties)
            {
                if (changedProperty.Attributes["Name"].Value == "Value")
                {
                    if (!Convert.ToBoolean(dtsProperties["EvaluateAsExpression"].GetValue(lockedVariables[variableName])))
                    {
                        dtsProperties[changedProperty.Attributes["Name"].Value].SetValue(lockedVariables[variableName],
                            Convert.ChangeType(changedProperty.Attributes["Value"].Value,
                                lockedVariables[variableName].DataType));
                    }
                }
                else
                {
                    dtsProperties[changedProperty.Attributes["Name"].Value].SetValue(lockedVariables[variableName],
                        Convert.ChangeType(changedProperty.Attributes["Value"].Value,
                            dtsProperties[changedProperty.Attributes["Name"].Value].Type));
                }
            }
        }

        private void UpdateNodeProperties(XmlDocument xmlDoc, XmlNode parentNode, string name, string value)
        {
            if (parentNode.ChildNodes.Count == 0)                            
                AddChildNode(xmlDoc, parentNode, "Properties");

            XmlNode propertiesNode = parentNode.ChildNodes[0];

            XmlNode propertyNode = AddChildNode(xmlDoc, propertiesNode, "Property");
            AddAttribute(xmlDoc, propertyNode, "Name", name);
            AddAttribute(xmlDoc, propertyNode, "Value", value);
        }

        private XmlNode AddChildNode(XmlDocument xmlDoc, XmlNode parentNode, string name)
        {
            parentNode.AppendChild(xmlDoc.CreateNode(XmlNodeType.Element, name, null));
            return parentNode.LastChild;
        }

        private void AddAttribute(XmlDocument xmlDoc, XmlNode parentNode, string name, string value)
        {
            XmlNode xmlAttrib = xmlDoc.CreateAttribute(name);
            xmlAttrib.InnerText = value;
            parentNode.Attributes.Append((XmlAttribute)xmlAttrib);
        }

        #endregion
    }
}
